#include "TestBufferWrapper.h"
#include <cstdlib>
#include <ctime>
#include <list>

#include <iostream>

TestBufferWrapper::TestBufferWrapper() {}
TestBufferWrapper::~TestBufferWrapper() {}

void TestBufferWrapper::TestAll() {
	
	CreateAndDestroy(8, 1000);
	TestPool(1000);
	TestStack(1000);
	TestManaged(1000);
}

void TestBufferWrapper::CreateAndDestroy(size_t elementSize, size_t capacity) {

	BufferWrapper* buffer = new BufferWrapper();
	assert(buffer != NULL && "Buffer couldnt be created");

	assert(buffer->isFree());
		
	buffer->createPoolBuffer(elementSize, capacity);
	assert(!buffer->isFree() && "Created pool buffer is still free");
	buffer->free();
	assert(buffer->isFree() && "Destroyed pool buffer is not free");

	buffer->createStackBuffer(elementSize, capacity);
	assert(!buffer->isFree() && "Created stack buffer is still free");
	buffer->free();
	assert(buffer->isFree() && "Destroyed stack buffer is not free");

	buffer->createManagedBuffer();
	assert(!buffer->isFree() && "Created managed buffer is still free");

	// buffer sizes for managed buffer:
	// 1. buffer - half element size, double capacity
	// 2. buffer - normal
	// 3. buffer - double element size, half capacity

	elementSize >>= 1;
	capacity <<= 1;
	for(unsigned int i=0; i<3; ++i) {
		elementSize <<= i;
		capacity >>= i;
		buffer->addPoolToManage(elementSize, capacity);
		assert(!buffer->isFree() && "Added a pool to managed buffer and now it's free");
	}
		
	buffer->free();
	assert(buffer->isFree() && "Destroyed managed buffer is not free");

	delete buffer;

}

void TestBufferWrapper::TestPool(size_t capacity) {
		
	BufferWrapper* buffer = new BufferWrapper();
	assert(buffer != NULL && "Pool buffer couldnt be created");

	assert(buffer->isFree());
		
	buffer->createPoolBuffer(sizeof(MediumData), capacity);
	assert(!buffer->isFree() && "Created pool buffer is still free");

	std::vector< Handle<> > handles(capacity);
	std::vector< MediumData* > pointers(capacity,0);
	MediumData testData, testData2;

	testData.random();
	testData2.random();

	for(unsigned int i=0; i<capacity; ++i) {
		dataId_t id;
		pointers[i] = buffer->acquire<MediumData>(id);
		*pointers[i] = testData;
		handles[i].init(0,id);
		buffer->setMagic(id,handles[i].getMagic());
	}

	for(unsigned int i=0; i<capacity; ++i) {
		MediumData* dereferenced = buffer->dereference<MediumData>(handles[i].getDataIndex(), handles[i].getMagic());
		//assert( pointers[i] == dereferenced && "Different pointer retrieved from dereference than acquire");
		assert( *pointers[i] == testData && "Assigned and retrieved data dont match");

		if(i%2 == 0) {
			*dereferenced = testData2;
		}
		else {
			buffer->release(handles[i].getDataIndex(), handles[i].getMagic());
		}
	}

	for(unsigned int i=0; i<capacity; i+=2) {
		MediumData* dereferenced = buffer->dereference<MediumData>(handles[i].getDataIndex(), handles[i].getMagic());
		//assert( pointers[i] == dereferenced && "Different pointer retrieved from dereference than acquire" );
		assert( *pointers[i] == testData2  && "Assigned and retrieved data dont match");

		buffer->release(handles[i].getDataIndex(), handles[i].getMagic());
	}

	buffer->free();
	assert(buffer->isFree() && "Destroyed pool buffer is not free");

	delete buffer;
}

void TestBufferWrapper::TestStack(size_t capacity) {

	BufferWrapper* buffer = new BufferWrapper();
	assert(buffer != NULL && "Stack buffer couldnt be created");

	assert(buffer->isFree());
		
	buffer->createStackBuffer(sizeof(SmallData), capacity);
	assert(!buffer->isFree() && "Created stack buffer is still free");

	std::vector< Handle<> > handles;
	std::vector< void* > pointers;
	std::vector< unsigned int > types;

	SmallData smallData;
	MediumData mediumData;
	LargeData largeData;
	
	smallData.random();
	mediumData.random();
	largeData.random();

	// acquire and assign data

	size_t dataSize = 0;
	for(unsigned int i=0; i + sizeof(LargeData) < capacity; i += dataSize) {
		dataId_t id;
		
		types.push_back(rand() % 3);

		switch(types.back()) {
		case 0: 
			{
				SmallData* data = buffer->acquire<SmallData>(id);
				*data = smallData;
				pointers.push_back(data);
				dataSize = sizeof(SmallData);
			}
			break;
		case 1:
			{
				MediumData* data = buffer->acquire<MediumData>(id);
				*data = mediumData;
				pointers.push_back(data);
				dataSize = sizeof(MediumData);
			}
			break;
		case 2:
			{
				LargeData* data = buffer->acquire<LargeData>(id);
				*data = largeData;
				pointers.push_back(data);
				dataSize = sizeof(LargeData);
			}
			break;
		}

		Handle<> handle;
		handle.init(0,id);
		handles.push_back(handle);
		buffer->setMagic(id,handle.getMagic());
	}

	// release in inverse order and check if that destroys non-released data

	for(int j=handles.size()-1; j>=0; --j) {

		// check remaining data
		for(unsigned int i=0; i<=j; ++i) {
			switch(types[i]) {
			case 0: 
				{
					SmallData* data = buffer->dereference<SmallData>(handles[i].getDataIndex(), handles[i].getMagic());
					assert(*data == smallData && "Assigned and retrieved data dont match");
				}
				break;
			case 1:
				{
					MediumData* data = buffer->dereference<MediumData>(handles[i].getDataIndex(), handles[i].getMagic());
					assert(*data == mediumData && "Assigned and retrieved data dont match");
				}
				break;
			case 2:
				{
					LargeData* data = buffer->dereference<LargeData>(handles[i].getDataIndex(), handles[i].getMagic());
					assert(*data == largeData && "Assigned and retrieved data dont match");
				}
				break;

			}
		}

		// release last
		buffer->release(handles[j].getDataIndex(), handles[j].getMagic());
	}
	
	buffer->free();
	assert(buffer->isFree() && "Destroyed pool buffer is not free");

	delete buffer;

}

void TestBufferWrapper::TestManaged(size_t capacity) {

	BufferWrapper* buffer = new BufferWrapper();
	assert(buffer != NULL && "Managed buffer couldnt be created");

	assert(buffer->isFree());
		
	buffer->createManagedBuffer();
	assert(!buffer->isFree() && "Created managed buffer is still free");

	buffer->addPoolToManage(sizeof(SmallData), capacity/3);
	buffer->addPoolToManage(sizeof(MediumData), capacity/3);		// test if it works with non-exact field sizes
	buffer->addPoolToManage(sizeof(LargeData), capacity/3);

	assert(!buffer->isFree() && "Added polls to managed buffer and now it's free");

	capacity = (capacity/3)*3;

	std::vector< Handle<> > handles;
	std::vector< unsigned int > types;
	std::vector< bool > usedIndices(capacity,true);

	SmallData smallData;
	MediumData mediumData;
	LargeData largeData;
	
	smallData.random();
	mediumData.random();
	largeData.random();

	// acquire and assign data

	size_t dataSize = 0;
	for(unsigned int i=0; i < capacity; ++i) {
		dataId_t id;
		
		types.push_back(i % 3);

		switch(types.back()) {
		case 0: 
			{
				SmallData* data = buffer->acquire<SmallData>(id);
				*data = smallData;
				dataSize = sizeof(SmallData);
			}
			break;
		case 1:
			{
				MediumData* data = buffer->acquire<MediumData>(id);
				*data = mediumData;
				dataSize = sizeof(MediumData);
			}
			break;
		case 2:
			{
				LargeData* data = buffer->acquire<LargeData>(id);
				*data = largeData;
				dataSize = sizeof(LargeData);
			}
			break;
		}

		Handle<> handle;
		handle.init(0,id);
		handles.push_back(handle);
		buffer->setMagic(id,handle.getMagic());
	}

	// release in random order and check if that destroys non-released data
	IndexVector* randomIndices = shuffle(handles.size());
	
	for(unsigned int j=0; j < randomIndices->size(); ++j) {

		// check remaining data
		for(unsigned int i=0; i<handles.size(); ++i) {
			if(usedIndices[i] == false) continue;

			switch(types[i]) {
			case 0: 
				{
					SmallData* data = buffer->dereference<SmallData>(handles[i].getDataIndex(), handles[i].getMagic());
					assert(*data == smallData && "Assigned and retrieved data dont match");
				}
				break;
			case 1:
				{
					MediumData* data = buffer->dereference<MediumData>(handles[i].getDataIndex(), handles[i].getMagic());
					assert(*data == mediumData && "Assigned and retrieved data dont match");
				}
				break;
			case 2:
				{
					LargeData* data = buffer->dereference<LargeData>(handles[i].getDataIndex(), handles[i].getMagic());
					assert(*data == largeData && "Assigned and retrieved data dont match");
				}
				break;

			}
		}

		// release random
		unsigned int ri = randomIndices->at(j);
		buffer->release(handles[ri].getDataIndex(), handles[ri].getMagic());
		usedIndices[ri] = false;
	}
	
	delete randomIndices;

	buffer->free();
	assert(buffer->isFree() && "Destroyed pool buffer is not free");

	delete buffer;

}